﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using MuZa.DBConnector;

namespace MuZa.MuZa
{
    internal class Controller
    {
        public InfoBoard board;
        public Initiator[] init;
        public Participant[] part;

        public static int numInit = 0;

        private AgentP[] _participantAgents;
        private AgentI[] _initiatorAgents;
        private List<Thread> _threadParticipantAgents = new List<Thread>();
        private List<Thread> _threadInitiatorAgents = new List<Thread>();

        /// <summary>
        /// ///////////
        /// </summary>
        ArrayList[] TransportAmountInitiaor = new ArrayList[3] { new ArrayList(), new ArrayList(), new ArrayList() };

        public Controller()
        {
            part = new Participant[4];
            init = new Initiator[3];
            board = new InfoBoard();

            _participantAgents = new AgentP[4];
            _initiatorAgents = new AgentI[3];
            _threadInitiatorAgents = new List<Thread>();
            _threadParticipantAgents = new List<Thread>();
        }

        public void Init()
        {
            part = new Participant[4];
            init = new Initiator[3];
            board = new InfoBoard();

            _participantAgents = new AgentP[4];
            _initiatorAgents = new AgentI[3];
            _threadInitiatorAgents = new List<Thread>();
            _threadParticipantAgents = new List<Thread>();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void AssignParticipants()
        {
            // for Participant 0
            part[0] = new Participant();
            part[0].setId("0");
            part[0].setName("part0");
            part[0].setLocation("Klaipeda");

            GetData.AddParticipant(part[0]);

            // for Participant 1
            part[1] = new Participant();
            part[1].setId("1");
            part[1].setName("part1");
            part[1].setLocation("Klaipeda");

            GetData.AddParticipant(part[1]);

            //for Participant 2
            part[2] = new Participant();
            part[2].setId("2");
            part[2].setName("part2");
            part[2].setLocation("Klaipeda");

            GetData.AddParticipant(part[2]);

            // for Participant 3
            part[3] = new Participant();
            part[3].setId("3");
            part[3].setName("part3");
            part[3].setLocation("Klaipeda");

            GetData.AddParticipant(part[3]);
        }

        private void AddParticipantToIB()
        {
            for (int i = 0; i < part.Length; i++)
            {
                GetData.AddParticipantToIB(part[i].getName());
                GetData.AddParticipantToTempIB(part[i].getName());
            }
        }

        private void AddResourcesForParticipant()
        {
            Resource res = new Resource("Product", "A", 200, 100, 15, "Klaipeda", "Klaipeda");
            part[0].addResourceToList(res);
            GetData.AddResourceToParticipant(part[0].getName(), res);

            /////////////////////////////////////////////////////////////////////////////////////////////////////////////
            res = new Resource("Transport", "Ferry", 500, 10, 14, "Klaipeda", "Karlshamn");
            part[1].addResourceToList(res);
            GetData.AddResourceToParticipant(part[1].getName(), res);

            /////////////////////////////////////////////////////////////////////////////////////////////////////////////
            res = new Resource("Transport", "Truck", 0.47, 25, 4, "Karlshamn", "Älmhult,Copenhagen,Esbjerg");
            part[2].addResourceToList(res);
            GetData.AddResourceToParticipant(part[2].getName(), res);

            /////////////////////////////////////////////////////////////////////////////////////////////////////////////
            res = new Resource("Transport", "Train", 4.73, 15, 2, "Karlshamn", "Älmhult");
            part[3].addResourceToList(res);
            GetData.AddResourceToParticipant(part[3].getName(), res);

            res = new Resource("Transport", "Train", 4.73, 15, 6, "Karlshamn", "Copenhagen,Esbjerg");
            part[3].addResourceToList(res);
            GetData.AddResourceToParticipant(part[3].getName(), res);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void AssignInitiators()
        {
            // for Initiator 0
            init[0] = new Initiator();
            init[0].setId("0");
            init[0].setName("init0");
            init[0].setLocation("Älmhult");
            init[0].setTotalCost(100000);
            init[0].setTotalDurationHours(36);

            GetData.AddInitiator(init[0]);

            init[0].addPriorityToList("DoAb");
            init[0].addPriorityToList("TotalCost");
            init[0].addPriorityToList("TotalDuration");

            GetData.AddPriorityToInitiator(init[0].getName(), 0.5, "DoAb");
            GetData.AddPriorityToInitiator(init[0].getName(), 0.25, "TotalCost");
            GetData.AddPriorityToInitiator(init[0].getName(), 0.25, "TotalDuration");

            // for Initiator 1
            init[1] = new Initiator();
            init[1].setId("1");
            init[1].setName("init1");
            init[1].setLocation("Copenhagen");
            init[1].setTotalCost(100000);
            init[1].setTotalDurationHours(36);

            GetData.AddInitiator(init[1]);

            init[1].addPriorityToList("DoAb");
            init[1].addPriorityToList("TotalCost");
            init[1].addPriorityToList("TotalDuration");

            GetData.AddPriorityToInitiator(init[1].getName(), 0.5, "DoAb");
            GetData.AddPriorityToInitiator(init[1].getName(), 0.3, "TotalCost");
            GetData.AddPriorityToInitiator(init[1].getName(), 0.2, "TotalDuration");

            // for Initiator 2
            init[2] = new Initiator();
            init[2].setId("2");
            init[2].setName("init2");
            init[2].setLocation("Esbjerg");
            init[2].setTotalCost(100000);
            init[2].setTotalDurationHours(36);

            GetData.AddInitiator(init[2]);

            init[2].addPriorityToList("DoAb");
            init[2].addPriorityToList("TotalCost");
            init[2].addPriorityToList("TotalDuration");

            GetData.AddPriorityToInitiator(init[2].getName(), 0.45, "DoAb");
            GetData.AddPriorityToInitiator(init[2].getName(), 0.3, "TotalCost");
            GetData.AddPriorityToInitiator(init[2].getName(), 0.25, "TotalDuration");
        }

        private void AddTaskForInitiator()
        {
            Task task = new Task("Product", "A", 25, "Klaipeda", "Klaipeda");
            init[0].addTaskToList(task);
            GetData.AddTaskToInitiator(init[0].getName(), task);

            task = new Task("Transport", "Ferry", 1, "Klaipeda", "Karlshamn");
            init[0].addTaskToList(task);
            GetData.AddTaskToInitiator(init[0].getName(), task);

            task = new Task("Transport", "", 4, "Karlshamn", "Älmhult");
            init[0].addTaskToList(task);
            GetData.AddTaskToInitiator(init[0].getName(), task);

            ///////////////////////////////////

            task = new Task("Product", "A", 25, "Klaipeda", "Klaipeda");
            init[1].addTaskToList(task);
            GetData.AddTaskToInitiator(init[1].getName(), task);

            task = new Task("Transport", "Ferry", 1, "Klaipeda", "Karlshamn");
            init[1].addTaskToList(task);
            GetData.AddTaskToInitiator(init[1].getName(), task);

            task = new Task("Transport", "", 2, "Karlshamn", "Copenhagen");
            init[1].addTaskToList(task);
            GetData.AddTaskToInitiator(init[1].getName(), task);

            /////////////////////////////////////

            task = new Task("Product", "A", 25, "Klaipeda", "Klaipeda");
            init[2].addTaskToList(task);
            GetData.AddTaskToInitiator(init[2].getName(), task);

            task = new Task("Transport", "Ferry", 1, "Klaipeda", "Karlshamn");
            init[2].addTaskToList(task);
            GetData.AddTaskToInitiator(init[2].getName(), task);

            task = new Task("Transport", "", 6, "Karlshamn", "Esbjerg");
            init[2].addTaskToList(task);
            GetData.AddTaskToInitiator(init[2].getName(), task);
        }

        private void RandomizeTaskForInitiator(int day)
        {
            List<int> rnSeqInitProductA = new List<int>();
            rnSeqInitProductA = GetData.GetRandomSequence(6 - day, init.Length, 1, 5, false);

            //for (int i = 0; i < init.Length; i++)
            //{
            //    Task task = new Task("Product", "A", rnSeqInitProductA[i], "Klaipeda", "Klaipeda");
            //    init[i].addTaskToList(task);
            //    GetData.AddTaskToInitiator(init[i].getName(), task);
            //}

            Task task = new Task("Product", "A", rnSeqInitProductA[0], "Klaipeda", "Klaipeda");
            init[0].addTaskToList(task);
            GetData.AddTaskToInitiator(init[0].getName(), task);

            task = new Task("Transport", "", rnSeqInitProductA[0], "Klaipeda", "Karlshamn");
            init[0].addTaskToList(task);
            GetData.AddTaskToInitiator(init[0].getName(), task);

            task = new Task("Transport", "", rnSeqInitProductA[0], "Karlshamn", "Älmhult");
            init[0].addTaskToList(task);
            GetData.AddTaskToInitiator(init[0].getName(), task);

            ///////////////////////////////////

            task = new Task("Product", "A", rnSeqInitProductA[1], "Klaipeda", "Klaipeda");
            init[1].addTaskToList(task);
            GetData.AddTaskToInitiator(init[1].getName(), task);

            task = new Task("Transport", "", rnSeqInitProductA[1], "Klaipeda", "Karlshamn");
            init[1].addTaskToList(task);
            GetData.AddTaskToInitiator(init[1].getName(), task);

            task = new Task("Transport", "", rnSeqInitProductA[1], "Karlshamn", "Copenhagen");
            init[1].addTaskToList(task);
            GetData.AddTaskToInitiator(init[1].getName(), task);

            /////////////////////////////////////

            task = new Task("Product", "A", rnSeqInitProductA[2], "Klaipeda", "Klaipeda");
            init[2].addTaskToList(task);
            GetData.AddTaskToInitiator(init[2].getName(), task);

            task = new Task("Transport", "", rnSeqInitProductA[2], "Klaipeda", "Karlshamn");
            init[2].addTaskToList(task);
            GetData.AddTaskToInitiator(init[2].getName(), task);

            task = new Task("Transport", "", rnSeqInitProductA[2], "Karlshamn", "Esbjerg");
            init[2].addTaskToList(task);
            GetData.AddTaskToInitiator(init[2].getName(), task);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////

        // For experiment 1
        private void IncreaseParticipantProductResourceQuantity(int day)
        {
            GetData.ReassignResourceQuantity("A", "Product", day, part[0].getName());
        }

        // For experiment 2
        //private void IncreaseInitiatorProductTaskQuantity(int day)
        //{
        //    //Random rn = new Random(15);
        //    //for (int i = 0; i < day; i++)
        //    //{
        //    //    int num = rn.Next(0, 3);
        //    //    GetData.ReassignTaskQuantity("A", "Product", 1, init[num].getName());
        //    //}
        //}

        private void IncreaseInitiatorTransportTaskQuantity(int day, int total)
        {
            /*
                            * Steps:
                             * 1. Get list of random numbers from distribution
                             * 2. Update amount of Transport 2
                            */

            double avarage = 15;
            int min = 1;
            int max = 25;
            double stdev = 1;

            if (day == 0)
            {
                //TransportAmountInitiaor[0] = Distribution.GetNormalList(stdev, avarage, min, max, total);
                //TransportAmountInitiaor[1] = Distribution.GetNormalList(stdev, avarage, min, max, total);
                //TransportAmountInitiaor[2] = Distribution.GetNormalList(stdev, avarage, min, max, total);
                TransportAmountInitiaor[0] = Distribution.GetPoissonList(avarage, min, max, total);
                TransportAmountInitiaor[1] = Distribution.GetPoissonList(avarage, min, max, total);
                TransportAmountInitiaor[2] = Distribution.GetPoissonList(avarage, min, max, total);
            }

            for (int i = 0; i < 3; i++)
            {
                GetData.ReassignTaskQuantity("", "Transport", Convert.ToInt32(TransportAmountInitiaor[i][day]), init[i].getName());
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void RunSim()
        {
            State tState = new State();
            int NoOfDays = 20;

            ////////////////////////////////////////////////////////////////////////////////////////////////////////
            GetData.ClearAllData();

            Thread.Sleep(100);
            _threadInitiatorAgents.Clear();
            Thread.Sleep(100);
            _threadParticipantAgents.Clear();
            Thread.Sleep(100);

            AssignParticipants();
            Thread.Sleep(100);

            AssignInitiators();
            Thread.Sleep(100);

            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            for (int day = 0; day < NoOfDays; day++)
            {
                GetData.ClearLogData();
                Thread.Sleep(100);

                //////////////////////////////////////////////////////////////////////////////////////////////////////
                _threadInitiatorAgents = new List<Thread>();
                _threadParticipantAgents = new List<Thread>();
                Thread.Sleep(100);

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                AddParticipantToIB();
                AddResourcesForParticipant();

                ///////////////////////////////////////////////////////////////////////////////////////////////////////
                // Fixed asking amount of resources in Task
                AddTaskForInitiator();

                // Randomizing asking amount for Tasks
                //RandomizeTaskForInitiator(day);

                ///////////////////////////////////////////////////////////////////////////////////////////////////////
                string experiment = "Exp2";

                // Experiment 1 ()
                // 1. check the resource quantity of Product A under Participant (function AddResourcesForParticipant)
                // 2. initialize task quantity for Product A as 1 for all the Initiators  (function AddTaskForInitiator)
                //IncreaseParticipantProductResourceQuantity(day);

                // Experiment 2 ()
                // 1. fix the resource quantity of Product A under Participant (function AddResourcesForParticipant)
                // 2. initialize task quantity for Product A as 1 for all the Initiators (function AddTaskForInitiator)
                //if (day > 0)
                //IncreaseInitiatorProductTaskQuantity(day);
                IncreaseInitiatorTransportTaskQuantity(day, NoOfDays);

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                Console.WriteLine("Starting day " + day);
                Thread.Sleep(2000);

                //if (day == 15)
                //    Console.WriteLine("Last");

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                //GetData.WriteLog("program", "", "simulation", "general", "strating agent-threads");
                GetData.numInit = 0;
                GetData.sw.Reset();
                GetData.sw.Start();

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                // Agent Initialization
                for (int i = 0; i < 4; i++)
                {
                    _participantAgents[i] = new AgentP(part[i].getName(), tState);
                    Thread aP = new Thread(new ThreadStart(_participantAgents[i].Run));
                    aP.Name = "Participant Thread " + i;
                    aP.IsBackground = true;

                    _threadParticipantAgents.Add(aP);
                    _threadParticipantAgents[i].Start();
                }

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                // Randomizind execution of the Initiators
                List<int> rnSeqInitThread = new List<int>();

                // To make the execution order manual
                //rnSeqInitThread.Add(2); rnSeqInitThread.Add(1); rnSeqInitThread.Add(0);

                // To make the Execution order random with seed
                rnSeqInitThread = GetData.GetRandomSequence(5 - day, 3, 0, 2, false);

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                for (int i = 0; i < 3; i++)
                {
                    _initiatorAgents[i] = new AgentI(init[i].getName(), init[i].getLocation(), init[i].getTotalCost(), init[i].getTotalDurationHours(), tState);
                    Thread aI = new Thread(new ThreadStart(_initiatorAgents[i].Run));
                    aI.Name = "Initiator Thread " + i;
                    aI.IsBackground = true;

                    _threadInitiatorAgents.Add(aI);
                    //_threadInitiatorAgents[i].Start();
                    //Thread.Sleep(1000);
                }

                for (int i = 0; i < 3; i++)
                {
                    _threadInitiatorAgents[rnSeqInitThread[i]].Start();
                    Thread.Sleep(1000);
                }

                while (GetData.numInit < 3)
                {
                    Thread.Sleep(1000);
                }

                StopSim();
                GetData.sw.Stop();
                Console.WriteLine("Simulation stopped in " + GetData.sw.Elapsed.Seconds + " seconds.");

                GetData.DataTable2CSV(GetData.GetAllResources(), experiment + "-Resources-Day-" + day + ".csv", ",");
                GetData.DataTable2CSV(GetData.GetAllTasks(), experiment + "-Tasks-Day-" + day + ".csv", ",");
                GetData.DataTable2CSV(GetData.GetAllMessages(), experiment + "-Messages-Day-" + day + ".csv", ",");
                GetData.DataTable2CSV(GetData.GetAllLogs(), experiment + "-Log-Day-" + day + ".csv", ",");
                //Thread.Sleep(3000);
            }
            //TimeSpan elapsedTime = sw.Elapsed;

            Application.Exit();
        }

        public void StopSim()
        {
            foreach (Thread td in _threadParticipantAgents)
            {
                td.Suspend();
                Console.WriteLine("Agent " + td.Name + " is stopped.");
            }
            foreach (Thread td in _threadInitiatorAgents)
            {
                td.Suspend();
                Console.WriteLine("Agent " + td.Name + " is stopped.");
            }
        }
    }

    internal class State
    {
        public bool Free { get; set; }

        public State()
        {
            this.Free = true;
        }
    }
}